<!DOCTYPE HTML>
<!--generated with sswg-->
<html lang="en">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<head>
    <title>platformer_tutorial</title>
    <link rel="stylesheet" href="sswg.css">
    <link rel="stylesheet" href="style.css">
    <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<div style="max-width:1200px; margin:auto;">
<div style="text-align:left;">
<br>
<a href=" documentation.html" class="button">← Back</a><br>
<h1 id="Platformer Tutorial">
Platformer Tutorial
</h1>Start by importing ursina and creating a window.<br>
<br>
<code_block id="code_block_0"><button class="copy_code_button" onclick="copy_to_clipboard(code_block_0)">copy</button><purple>from</purple> ursina <purple>import</purple> *
app = Ursina()
</code_block>
<br>
<h2 id="Using the built in platformer controller">
Using the built in platformer controller
</h2><br>
A simple way to get stared is to use the built in platformer controller.<br>
It's pretty basic, so you might want to write your own at a later point.<br>
It is however a good starting point, so let's import it like this:<br>
<br>
<code_block id="code_block_1"><button class="copy_code_button" onclick="copy_to_clipboard(code_block_1)">copy</button><purple>from</purple> ursina.prefabs.platformer_controller_<yellow>2</yellow>d <purple>import</purple> PlatformerController<yellow>2</yellow>d
player = PlatformerController<yellow>2</yellow>d(<olive>y</olive>=<yellow>1</yellow>, <olive>z</olive>=.<yellow>0</yellow><yellow>1</yellow>, scale_<olive>y</olive>=<yellow>1</yellow>, max_jumps=<yellow>2</yellow>)
</code_block>
<br>
You can change settings like jump_height, walk_speed, and gravity.<br>
If you want to larn more about how it works you can read its code here:<br>
<a href="https://github.com/pokepetter/ursina/blob/master/ursina/prefabs/platformer_controller_2d.py">https://github.com/pokepetter/ursina/blob/master/ursina/prefabs/platformer_controller_2d.py</a><br>
<br>
If we try to play the game right now, you'll fall for all infinity, so let's add a ground:<br>
<br>
<code_block id="code_block_2"><button class="copy_code_button" onclick="copy_to_clipboard(code_block_2)">copy</button>ground = <olive>Entity</olive>(<olive>model</olive>=<green>'quad'</green>, <olive>scale_x</olive>=<yellow>1</yellow><yellow>0</yellow>, <olive>collider</olive>=<green>'box'</green>, <olive>color</olive>=color.black)
</code_block>
<br>
<h2 id="Making a "level editor"">
Making a "level editor"
</h2><br>
Now, it works, but it's a pretty boring game, so let's make a more interesting level.<br>
There are many ways to go about making a level, but for this we'll make an image<br>
where we can simply draw the level and then generate a level based on that.<br>
<br>
<img src="platformer_tutorial_level.png"></img> <br>
↑<br>
Make sure to save this image to same folder or below as your script.<br>
<br>
To generate the level we'll loop through all the pixels in the image above and do<br>
something based on the color of the pixel. If it's white, it's air, so we'll skip it.<br>
Now, we *could* create an Entity for each tile, but that's slower to render than one Entity with a custom model.<br>
To make the model, we'll use the Mesh class. You'll have to be somewhat familiar with<br>
what meshes are to do this, and know what vertices and uvs are. However, we'll just be<br>
copying the quad model and offset the vertices. If we wanted to use a tileset with different<br>
graphics for each tile, we'd scale and offset the uvs as well.<br>
<br>
<code_block id="code_block_3"><button class="copy_code_button" onclick="copy_to_clipboard(code_block_3)">copy</button>
quad = load_model(<green>'quad'</green>, use_deepcop<olive>y</olive>=True)


level_parent = <olive>Entity</olive>(<olive>model</olive>=Mesh(<olive>vertices</olive>=[], <olive>uvs</olive>=[]), <olive>texture</olive>=<green>'white_cube'</green>)
<purple>def</purple> make_level(texture):
&nbsp;&nbsp;&nbsp;&nbsp;<gray># destroy every child of the level parent.</gray>
&nbsp;&nbsp;&nbsp;&nbsp;<gray># This doesn't do anything the first time the level is generated, but <purple>if</purple> we want to update it several times</gray>
&nbsp;&nbsp;&nbsp;&nbsp;<gray># this will ensure it doesn't just create a bunch of overlapping entities.</gray>
&nbsp;&nbsp;&nbsp;&nbsp;[destroy(c) <purple>for</purple> c in level_parent.children]

&nbsp;&nbsp;&nbsp;&nbsp;<purple>for</purple> y in <blue>range</blue>(texture.height):
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;collider = None
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<purple>for</purple> x in <blue>range</blue>(texture.width):
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;col = texture.get_pixel(x,y)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<gray># If it<green>'s black, it'</green>s solid, so we'll place a tile there.</gray>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<purple>if</purple> col == color.black:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;level_parent.model.vertices += [Vec3(*e) + Vec3(x+.<yellow>5</yellow>,y+.<yellow>5</yellow>,<yellow>0</yellow>) <purple>for</purple> e in quad.generated_vertices] <gray># copy the quad model, but offset it with Vec3(x+.<yellow>5</yellow>,y+.<yellow>5</yellow>,<yellow>0</yellow>)</gray>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;level_parent.model.uvs += quad.uvs
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<gray># <olive>Entity</olive>(<olive>parent</olive>=level_parent, <olive>position</olive>=(x,y), <olive>model</olive>=<green>'cube'</green>, <olive>origin</olive>=(-.<yellow>5</yellow>,-.<yellow>5</yellow>), <olive>color</olive>=color.gray, <olive>texture</olive>=<green>'white_cube'</green>, <olive>visible</olive>=True)</gray>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<purple>if</purple> <purple>not</purple> collider:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;collider = <olive>Entity</olive>(<olive>parent</olive>=level_parent, <olive>position</olive>=(x,y), <olive>model</olive>=<green>'cube'</green>, <olive>origin</olive>=(-.<yellow>5</yellow>,-.<yellow>5</yellow>), <olive>collider</olive>=<green>'box'</green>, <olive>visible</olive>=False)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<purple>else</purple>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<gray># instead of creating a new collider per tile, stretch the previous collider right.</gray>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;collider.scale_x += <yellow>1</yellow>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<purple>else</purple>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;collider = None

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<gray># If it<green>'s green, we'</green>ll place the player there. Store this in player.start_position so we can reset the plater position later.</gray>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<purple>if</purple> col == color.green:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;player.start_position = (x, y)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;player.position = player.start_position

&nbsp;&nbsp;&nbsp;&nbsp;level_parent.model.generate()

make_level(load_texture(<green>'platformer_tutorial_level'</green>))&nbsp;&nbsp; <gray># generate the level</gray>
</code_block>
<br>
<h2 id="Positioning the camera">
Positioning the camera
</h2><br>
Set the camera to orthographic so there's no perspective.<br>
Move the camera to the middle of the level and set the fov so the level fits nicely.<br>
Setting the fov on an orthographic camera means setting how many units vertically the camera can see.<br>
<br>
<code_block id="code_block_4"><button class="copy_code_button" onclick="copy_to_clipboard(code_block_4)">copy</button>camera.orthographic = True
camera.position = (<yellow>3</yellow><yellow>0</yellow>/<yellow>2</yellow>,<yellow>8</yellow>)
camera.fov = <yellow>1</yellow><yellow>6</yellow>
</code_block>
<br>
<h2 id="Adding player graphics and animations">
Adding player graphics and animations
</h2><br>
Loads an image sequence as a frame animation.<br>
So if you have some frames named image_000.png, image_001.png, image_002.png and so on,<br>
you can load it like this: Animation('image')<br>
You can also load a .gif by including the file type: Animation('image.gif')<br>
player.walk_animation = Animation('player_walk')<br>
<br>
<code_block id="code_block_5"><button class="copy_code_button" onclick="copy_to_clipboard(code_block_5)">copy</button>
</code_block>
<br>
the platformer controller has an Animator and will toggle the state based on<br>
whether it's standing still, is walking or is jumping.<br>
All the Animator does is to make sure only one Animation is enabled at the same time.<br>
Otherwise they would overlap.<br>
self.animator = Animator({'idle' : None, 'walk' : None, 'jump' : None})<br>
<br>
<code_block id="code_block_6"><button class="copy_code_button" onclick="copy_to_clipboard(code_block_6)">copy</button>player.traverse_target = level_parent
enemy = <olive>Entity</olive>(<olive>model</olive>=<green>'cube'</green>, <olive>collider</olive>=<green>'box'</green>, <olive>color</olive>=color.red, <olive>position</olive>=(<yellow>1</yellow><yellow>6</yellow>,<yellow>5</yellow>,-.<yellow>1</yellow>))
<purple>def</purple> update():
&nbsp;&nbsp;&nbsp;&nbsp;<purple>if</purple> player.intersects(enemy).hit:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<blue>print</blue>(<green>'die'</green>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;player.position = player.start_position
</code_block>
<br>
<h2 id="Start the game">
Start the game
</h2><br>
<br>
<code_block id="code_block_7"><button class="copy_code_button" onclick="copy_to_clipboard(code_block_7)">copy</button>EditorCamera()
app.run()
</code_block>
<br>
<h2 id="Result">
Result
</h2><br>
<code_block id="code_block_8"><button class="copy_code_button" onclick="copy_to_clipboard(code_block_8)">copy</button><purple>from</purple> ursina <purple>import</purple> *
app = Ursina()

<purple>from</purple> ursina.prefabs.platformer_controller_<yellow>2</yellow>d <purple>import</purple> PlatformerController<yellow>2</yellow>d
player = PlatformerController<yellow>2</yellow>d(<olive>y</olive>=<yellow>1</yellow>, <olive>z</olive>=.<yellow>0</yellow><yellow>1</yellow>, scale_<olive>y</olive>=<yellow>1</yellow>, max_jumps=<yellow>2</yellow>)

ground = <olive>Entity</olive>(<olive>model</olive>=<green>'quad'</green>, <olive>scale_x</olive>=<yellow>1</yellow><yellow>0</yellow>, <olive>collider</olive>=<green>'box'</green>, <olive>color</olive>=color.black)


quad = load_model(<green>'quad'</green>, use_deepcop<olive>y</olive>=True)


level_parent = <olive>Entity</olive>(<olive>model</olive>=Mesh(<olive>vertices</olive>=[], <olive>uvs</olive>=[]), <olive>texture</olive>=<green>'white_cube'</green>)
<purple>def</purple> make_level(texture):
&nbsp;&nbsp;&nbsp;&nbsp;<gray># destroy every child of the level parent.</gray>
&nbsp;&nbsp;&nbsp;&nbsp;<gray># This doesn't do anything the first time the level is generated, but <purple>if</purple> we want to update it several times</gray>
&nbsp;&nbsp;&nbsp;&nbsp;<gray># this will ensure it doesn't just create a bunch of overlapping entities.</gray>
&nbsp;&nbsp;&nbsp;&nbsp;[destroy(c) <purple>for</purple> c in level_parent.children]

&nbsp;&nbsp;&nbsp;&nbsp;<purple>for</purple> y in <blue>range</blue>(texture.height):
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;collider = None
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<purple>for</purple> x in <blue>range</blue>(texture.width):
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;col = texture.get_pixel(x,y)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<gray># If it<green>'s black, it'</green>s solid, so we'll place a tile there.</gray>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<purple>if</purple> col == color.black:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;level_parent.model.vertices += [Vec3(*e) + Vec3(x+.<yellow>5</yellow>,y+.<yellow>5</yellow>,<yellow>0</yellow>) <purple>for</purple> e in quad.generated_vertices] <gray># copy the quad model, but offset it with Vec3(x+.<yellow>5</yellow>,y+.<yellow>5</yellow>,<yellow>0</yellow>)</gray>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;level_parent.model.uvs += quad.uvs
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<gray># <olive>Entity</olive>(<olive>parent</olive>=level_parent, <olive>position</olive>=(x,y), <olive>model</olive>=<green>'cube'</green>, <olive>origin</olive>=(-.<yellow>5</yellow>,-.<yellow>5</yellow>), <olive>color</olive>=color.gray, <olive>texture</olive>=<green>'white_cube'</green>, <olive>visible</olive>=True)</gray>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<purple>if</purple> <purple>not</purple> collider:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;collider = <olive>Entity</olive>(<olive>parent</olive>=level_parent, <olive>position</olive>=(x,y), <olive>model</olive>=<green>'cube'</green>, <olive>origin</olive>=(-.<yellow>5</yellow>,-.<yellow>5</yellow>), <olive>collider</olive>=<green>'box'</green>, <olive>visible</olive>=False)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<purple>else</purple>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<gray># instead of creating a new collider per tile, stretch the previous collider right.</gray>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;collider.scale_x += <yellow>1</yellow>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<purple>else</purple>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;collider = None

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<gray># If it<green>'s green, we'</green>ll place the player there. Store this in player.start_position so we can reset the plater position later.</gray>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<purple>if</purple> col == color.green:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;player.start_position = (x, y)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;player.position = player.start_position

&nbsp;&nbsp;&nbsp;&nbsp;level_parent.model.generate()

make_level(load_texture(<green>'platformer_tutorial_level'</green>))&nbsp;&nbsp; <gray># generate the level</gray>

camera.orthographic = True
camera.position = (<yellow>3</yellow><yellow>0</yellow>/<yellow>2</yellow>,<yellow>8</yellow>)
camera.fov = <yellow>1</yellow><yellow>6</yellow>



player.traverse_target = level_parent
enemy = <olive>Entity</olive>(<olive>model</olive>=<green>'cube'</green>, <olive>collider</olive>=<green>'box'</green>, <olive>color</olive>=color.red, <olive>position</olive>=(<yellow>1</yellow><yellow>6</yellow>,<yellow>5</yellow>,-.<yellow>1</yellow>))
<purple>def</purple> update():
&nbsp;&nbsp;&nbsp;&nbsp;<purple>if</purple> player.intersects(enemy).hit:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<blue>print</blue>(<green>'die'</green>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;player.position = player.start_position


EditorCamera()
app.run()


</code_block>
<script>
function copy_to_clipboard(containerid) {
    var range = document.createRange()
    range.selectNode(containerid)
    window.getSelection().removeAllRanges()
    window.getSelection().addRange(range)
    document.execCommand("copy")
    window.getSelection().removeAllRanges()
}
</script>
<br>
<br>
</body>
</html>